﻿using System;
using System.Reflection;
using System.Runtime.InteropServices.Expando;
using System.Runtime.InteropServices.ComTypes;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Net;
using System.Linq;
using System.Text;
using BrainTechLLC.ImageOperatorsNonAForgeDependent;
using BrainTechLLC;
using System.Configuration;
using System.Diagnostics;
using BrainTechLLC.ThreadSafeObjects;
using System.Windows.Forms;
using System.Windows.Threading;

namespace BrainTechLLC.WebCamLib3
{
    public class CameraHandler : BaseNotifyPropertyChanged, IDisposable, IImageProcessor, INotifyPropertyChanged
    {
        public FastSmartWeakEvent<EventHandler<ObjectEventArgs<List<IPostProcessor>>>> _gatherPostProcessors = new FastSmartWeakEvent<EventHandler<ObjectEventArgs<List<IPostProcessor>>>>();
        public event EventHandler<ObjectEventArgs<List<IPostProcessor>>> GatherPostProcessors { add { _gatherPostProcessors.Add(value); } remove { _gatherPostProcessors.Remove(value); } }

        public IVideoFeed _videoFeed;
        public event EventHandler<ObjectEventArgs<FrameInfo>> NewFrameReceived;

        protected double _lastFrameTime = 0.0;
        protected double _currentTimeBetweenFrames = 0.0;
        public int _framesProcessed;

        int _minimumProcessSleepTime = 20;

        protected Stopwatch _stopWatch = new Stopwatch();
        protected int _waitForNextImageSleepTimeOverride = 30;
        protected int _waitForNextImageSleepTime = 30;
        protected ThreadPriority _processingThreadPriority = ThreadPriority.Normal;
        protected bool _wantExit = false;
        protected bool _currentlyProcessing = false;
        protected Thread processImageThread = null;
        protected DateTime _lastProcessedFrame = DateTime.MinValue;
        protected RotateFlipType _frameImageFlipType = RotateFlipType.RotateNoneFlipNone; //= RotateFlipType.Rotate180FlipNone;
        protected int _sleepTimeRetyCheckCameraStarted = 50;

        public ThreadSafeLookup<string, IPostProcessor> PostProcessors = new ThreadSafeLookup<string, IPostProcessor>();

        public int FramesProcessed
        {
            get { return _framesProcessed; }
        }

        public bool VideoCaptureProcessing
        {
            get
            {
                if (_videoFeed == null)
                    return false;

                return _videoFeed.IsProcessing;
            }
        }

        public Exception VideoCaptureError
        {
            get
            {
                if (_videoFeed == null)
                    return null;

                return _videoFeed.CameraError;
            }
        }

        public CameraHandler()
        {
        }

        public void Dispose()
        {
            StopProcessingImages();

            _videoFeed.Dispose();

            if (_stopWatch != null)
            {
                if (_stopWatch.IsRunning)
                {
                    _stopWatch.Stop();
                }

                _stopWatch = null;
            }
        }

        public int MinimumProcessSleepTime
        {
            get
            {
                return _minimumProcessSleepTime;
            }
            set
            {
                if (_minimumProcessSleepTime != value)
                {
                    _minimumProcessSleepTime = value;
                    OnPropertyChanged("MinimumProcessSleepTime");
                }
            }
        }

        public void ResetFPS()
        {
            _framesProcessed = 0;
            _stopWatch.Reset();
            _stopWatch.Start();
        }

        public double FPS
        {
            get { return CalcFPS(); }
        }

        public double CalcFPS()
        {
            double fps = 0;

            try
            {
                double frames = (double)_framesProcessed;
                double milliseconds = _stopWatch.ElapsedMilliseconds;

                if (milliseconds > 0)
                {
                    fps = (frames * 1000D) / milliseconds;
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            return fps;
        }

        [Category("[Program Control]"), Description(""), Browsable(false)]
        public bool CurrentlyProcessing { get { return _currentlyProcessing; } }

        [Category("[Video Stats]"), Description(""), ApplicationScopedSetting()]
        public double CurrentTimeBetweenFrames { get { return _currentTimeBetweenFrames; } }

        [Category("[Program Control]"), Description(""), DisplayName("(1) Sleep Duration between processing frames")]
        public int WaitForNextImageSleepTime { get { return _waitForNextImageSleepTime; } set { _waitForNextImageSleepTime = value; } }

        [Category("[Program Control"), DisplayName("(2) Override Image Sleep Duration]"), Description("If set to a nonzero value, image processing will not adjust the sleep speed to minimize processor usage.")]
        public int WaitForNextImageSleepTimeOverride { get { return _waitForNextImageSleepTimeOverride; } set { _waitForNextImageSleepTimeOverride = value; } }

        public int SleepTimeRetyCheckCameraStarted
        {
            get
            {
                return _sleepTimeRetyCheckCameraStarted;
            }
            set
            {
                if (_sleepTimeRetyCheckCameraStarted != value)
                {
                    _sleepTimeRetyCheckCameraStarted = value;
                    OnPropertyChanged("SleepTimeRetyCheckCameraStarted");
                }
            }
        }

        public RotateFlipType FrameImageFlipType
        {
            get
            {
                return _frameImageFlipType;
            }
            set
            {
                if (_frameImageFlipType != value)
                {
                    _frameImageFlipType = value;
                    OnPropertyChanged("FrameImageFlipType");
                }
            }
        }

        public DateTime LastProcessedFrame
        {
            get
            {
                return _lastProcessedFrame;
            }
            protected set
            {
                if (_lastProcessedFrame != value)
                {
                    _lastProcessedFrame = value;
                    OnPropertyChanged("LastProcessedFrame");
                }
            }
        }

        [Category("[Program Control]"), Description(""), ApplicationScopedSetting()]
        public bool WantExit
        {
            get { return _wantExit; }
            set
            {
                if (value == true)
                {
                    try
                    {
                        _videoFeed.StopCameraCapture();
                    }
                    catch { }
                    try
                    {
                        _videoFeed.TerminateCameraCapture();
                    }
                    catch { }

                    _wantExit = true;
                    Thread.Sleep(100);
                }
            }
        }

        [Category("[Program Control]"), Description("This setting controls the priority of the thread that processes incoming images before they are displayed."), DisplayName("Image Processing Priority"), ApplicationScopedSetting()]
        public ThreadPriority ProcessingThreadPriority
        {
            get { return _processingThreadPriority; }
            set
            {
                if (_currentlyProcessing) { processImageThread.Priority = value; }
                _processingThreadPriority = value;
            }
        }

        ~CameraHandler()
        {

            try
            {
                _videoFeed.SignalCameraToStop();
                Thread.Sleep(100);
            }
            catch { }
            try
            {
                _videoFeed.TerminateCameraCapture();
                Thread.Sleep(100);
            }
            catch { }
        }

        private void BeginProcessing()
        {
            if (_currentlyProcessing == true) return;
            ThreadStart ts = new ThreadStart(ProcessImages);
            processImageThread = new Thread(ts);
            processImageThread.TrySetApartmentState(ApartmentState.MTA);
            //processImageThread.TrySetApartmentState(ApartmentState.STA);
            processImageThread.Name = "Video Processing Thread for device " + _videoFeed.CameraMoniker;
            processImageThread.Priority = _processingThreadPriority;
            processImageThread.IsBackground = true;
            processImageThread.Start();
            _currentlyProcessing = true;
        }

        private void ProcessImages()
        {
            try
            {
                int CyclesWithNoImages = 0;
                double frameTime = 0.0;
                bool firstFrame = true;

                // need all postprocessors to be set when starting the processing handler for the moment;
                var eventArgs = new ObjectEventArgs<List<IPostProcessor>>(new List<IPostProcessor>());
                _gatherPostProcessors.Raise(this, eventArgs);

                for (int n = 0; n < eventArgs.obj.Count; n++)
                {
                    PostProcessors.Add(eventArgs.obj[n].Name, eventArgs.obj[n]);
                }
                
                var postProcessors = PostProcessors.AllItems;

                while (_videoFeed == null || _videoFeed.IncomingVideoWidth == -1 && !_wantExit)
                {
                    Thread.Sleep(50);
                }

                while (!_wantExit)
                {
                    FrameInfo frameInfo = _videoFeed.GetNextFrame();

                    if (frameInfo != null)
                    {
                        if (firstFrame)
                        {
                            firstFrame = false;
                            _stopWatch.Start();
                        }
                        frameTime = frameInfo.ImageFrameTime;
                        LastProcessedFrame = DateTime.Now;
                    }
                    else
                    {
                        frameTime = 0.0;

                        if (_videoFeed.IncomingVideoWidth == -1)
                        {
                            // No webcam... check occasionally to see if there is a remote video source available
                            CyclesWithNoImages++;
                            Thread.Sleep(SleepTimeRetyCheckCameraStarted);
                            continue;
                        }
                        else
                        {
                            if (CyclesWithNoImages < 0)
                            {
                                CyclesWithNoImages = 0;
                            }
                        }

                        Thread.Sleep((WaitForNextImageSleepTimeOverride > 0) ? WaitForNextImageSleepTimeOverride : WaitForNextImageSleepTime);
                        continue;
                    }

                    if (_videoFeed.IncomingVideoWidth != -1)
                    {
                        if (CyclesWithNoImages > 0)
                        {
                            CyclesWithNoImages = 0;
                        }

                        if (_lastFrameTime != 0.0)
                        {
                            _currentTimeBetweenFrames = frameTime - _lastFrameTime;
                        }

                        _lastFrameTime = frameTime;
                    }

                    CyclesWithNoImages--;

                    Bitmap bmOriginal = frameInfo.Bitmap;
                    bool flipped = false;

                    try
                    {
                        if (FrameImageFlipType != RotateFlipType.RotateNoneFlipNone && frameInfo.Bitmap != null)
                        {
                            frameInfo.Bitmap.RotateFlip(FrameImageFlipType);
                            flipped = true;
                        }

                        for (int n = 0; n < postProcessors.Count; n++)
                        {
                            var postProcessor = postProcessors[n];

                            if (!postProcessor.Disabled)
                            {
                                postProcessor.ProcessBitmap(frameInfo);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.ToString());
                    }

                    Interlocked.Increment(ref _framesProcessed);
                    OnPropertyChanged("FramesProcessed", false);

                    if (flipped || frameInfo.Bitmap != bmOriginal)
                    {
                        Bitmap bm = frameInfo.Bitmap;
                        Interlocked.Exchange<byte[]>(ref frameInfo._bytes, bm.CreateBytes());
                        frameInfo._bitmap = null;
                    }

                    OnFrameReceived(frameInfo);

                    // TODO - should vary based on time & how long raising the frame received event to execute.
                    // Or maybe it should be raised in another thread...

                    if (_videoFeed.IncomingFrameQueueCount > 2)
                    {
                        Thread.Sleep(_minimumProcessSleepTime);
                    }
                    else if (_videoFeed.IncomingFrameQueueCount >= 1)
                    {
                        Thread.Sleep(_minimumProcessSleepTime + 10);
                    }
                    else
                    {
                        Thread.Sleep(_minimumProcessSleepTime + 20);
                    }

                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }

            _currentlyProcessing = false;
        }

        protected virtual void OnFrameReceived(FrameInfo frameInfo)
        {
            if (this.NewFrameReceived != null)
                NewFrameReceived(this, new ObjectEventArgs<FrameInfo>(frameInfo));
        }

        public void StopCameraCapture()
        {
            if (_videoFeed != null)
            {
                _videoFeed.StopCameraCapture();
            }
        }

        public bool BeginProcessingImages(out string error)
        {
            if (_videoFeed == null)
            {
                error = "The camera capture thread has not yet been started.  Use StartCameraCapture or OpenCaptureDevice to start the camera thread";
                return false;
            }

            BeginProcessing();
            error = null;
            return true;
        }

        /// <summary>
        /// Stops all image processing and closes the video capture source
        /// Equivalent to setting WantExit = true
        /// </summary>    
        public void StopProcessingImages()
        {
            WantExit = true;
        }

        public bool OpenCaptureDevice(LocalVideoDevice videoDeviceToUse, out Exception ex)
        {
            if (_videoFeed != null && CurrentlyProcessing == true)
            {
                try
                {
                    _videoFeed.SignalCameraToStop();
                    Thread.Sleep(200);
                    _videoFeed.TerminateCameraCapture();
                    Thread.Sleep(100);
                }
                catch (Exception bummer)
                {
                    Debug.Assert(false, bummer.ToString());
                    // keep going in release mode
                }
                finally
                {
                    _videoFeed = null;
                }
            }

            VideoCapture videoCap = new VideoCapture();
            var result = videoCap.OpenCaptureDevice(videoDeviceToUse, out ex);

            _videoFeed = videoCap;
            return result;
        }
    }
}
